import java.util.ArrayList;
import java.util.Set;

/**
 * Produce some standard quivers.
 * Taken from http://en.wikipedia.org/wiki/Gallery_of_named_graphs
 * and http://en.wikipedia.org/wiki/Dynkin_diagram
 */
public class StandardQuivers {
	private static final double startX = 50, startY = 150, dist = 100,
			circleRadius = 100;

	public static enum Type {
		A, D, E, CYCLE, WHEEL, FRIENDSHIP, STAR, PYRAMID, COMPLETE
	};

	public static Quiver create(Type type, int n) {
		switch (type) {
		case A:
			return createAn(n, true);
		case D:
			return createDn(n, true);
		case E:
			return createEn(n, true);
		case CYCLE:
			return createCycle(n, true);
		case WHEEL:
			return createWheel(n, true);
		case STAR:
			return createStar(n, true);
		case FRIENDSHIP:
			return createFriendship(n, true);
		case PYRAMID:
			return createPyramid(n, true);
		case COMPLETE:
			return createComplete(n);
		}
		return null;
	}

	private static Quiver createAn(int n, boolean withArrows) {
		if (n < 0) {
			return null;
		}
		Quiver quiver = new Quiver("A" + n);
		Node oldNode = null;
		for (int i = 0; i < n; i++) {
			Node node = quiver.addNode(startX + dist * i, startY);
			if (i > 0 && withArrows) {
				quiver.addArrow(oldNode, node);
			}
			oldNode = node;
		}
		return quiver;
	}

	private static Quiver createDn(int n, boolean withArrows) {
		if (n < 3) {
			return createAn(n, withArrows);
		}
		Quiver quiver = new Quiver("D" + n);
		Node oldNode = null;
		for (int i = 0; i < n - 2; i++) {
			Node node = quiver.addNode(startX + dist * i, startY);
			if (i > 0 && withArrows) {
				quiver.addArrow(oldNode, node);
			}
			oldNode = node;
		}
		Node node1 = quiver.addNode(startX + dist * (n - 2), startY - dist);
		Node node2 = quiver.addNode(startX + dist * (n - 2), startY + dist);
		if (withArrows) {
			quiver.addArrow(oldNode, node1);
			quiver.addArrow(oldNode, node2);
		}
		return quiver;
	}

	private static Quiver createEn(int n, boolean withArrows) {
		if (n < 5) {
			return createAn(n, withArrows);
		}
		Quiver quiver = new Quiver("E" + n);
		Node oldNode = null;
		for (int i = 0; i < n - 1; i++) {
			Node node = quiver.addNode(startX + dist * i, startY);
			if (i > 0 && withArrows) {
				quiver.addArrow(oldNode, node);
			}
			if (i == 2) {
				Node armNode = quiver.addNode(startX + dist * i, startY - dist);
				if (withArrows) {
					quiver.addArrow(node, armNode);
				}
			}
			oldNode = node;
		}
		return quiver;
	}

	private static Quiver createCycle(int n, boolean withArrows) {
		if (n < 2) {
			return createAn(n, withArrows);
		}
		Quiver quiver = new Quiver("C" + n);
		Node firstNode = null, lastNode = null, prevNode = null;
		for (int i = 0; i < n; i++) {
			double angle = i * 2 * Math.PI / n;
			Node node = quiver.addNode(startX + circleRadius + circleRadius
					* Math.cos(angle), startY + circleRadius / 2 + circleRadius
					* Math.sin(angle));
			if (i == 0) {
				firstNode = node;
			} else if (withArrows) {
				quiver.addArrow(prevNode, node);
			}
			if (i == n - 1) {
				lastNode = node;
			}
			prevNode = node;
		}
		if (withArrows) {
			quiver.addArrow(lastNode, firstNode);
		}
		return quiver;
	}

	private static Quiver createWheel(int n, boolean withArrows) {
		if (n < 4) {
			return createCycle(n, withArrows);
		}
		Quiver quiver = createCycle(n - 1, withArrows);
		quiver.setName("W" + n);
		Node newNode = quiver.addNode(startX + circleRadius, startY
				+ circleRadius / 2);
		if (withArrows) {
			for (Node node : quiver.getNodes()) {
				if (node != newNode) {
					quiver.addArrow(node, newNode);
				}
			}
		}
		return quiver;
	}

	private static Quiver createStar(int n, boolean withArrows) {
		if (n < 3) {
			return createCycle(n, false);
		}
		Quiver quiver = createCycle(n - 1, false);
		quiver.setName("S" + n);
		Node newNode = quiver.addNode(startX + circleRadius, startY
				+ circleRadius / 2);
		if (withArrows) {
			for (Node node : quiver.getNodes()) {
				if (node != newNode) {
					quiver.addArrow(node, newNode);
				}
			}
		}
		return quiver;
	}

	private static Quiver createFriendship(int n, boolean withArrows) {
		if (n < 1) {
			return createAn(n, true);
		}
		if (n == 1){
			return createCycle(3, true);
		}
		Quiver quiver = new Quiver("F" + n);
		Node node = quiver.addNode(startX + circleRadius, startY + circleRadius
				/ 2);
		for (int i = 0; i < n; i++) {
			double angle1 = 2 * i * 2 * Math.PI / (2 * n);
			Node node1 = quiver.addNode(startX + circleRadius + circleRadius
					* Math.cos(angle1), startY + circleRadius / 2
					+ circleRadius * Math.sin(angle1));
			double angle2 = (2 * i + 1) * 2 * Math.PI / (2 * n);
			Node node2 = quiver.addNode(startX + circleRadius + circleRadius
					* Math.cos(angle2), startY + circleRadius / 2
					+ circleRadius * Math.sin(angle2));
			if (withArrows) {
				quiver.addArrow(node1, node2);
				quiver.addArrow(node, node1);
				quiver.addArrow(node2, node);
			}
		}
		return quiver;
	}

	private static Quiver createPyramid(int n, boolean withArrows) {
		Quiver quiver = new Quiver("Pyr" + n);
		int layers = (int) Math.ceil(((-1 + Math.sqrt(1 + 8 * n)) / 2));
		int numNodes = 0;
		ArrayList<Node> oldLayer = null;
		for (int j = layers; j > 0; j--) {
			if (numNodes == n) {
				break;
			}
			Node oldNode = null;
			ArrayList<Node> layer = new ArrayList<Node>();
			for (int i = 0; i < j; i++) {
				if (numNodes == n) {
					break;
				}
				Node node = quiver.addNode(startX + i * dist + (dist / 2)
						* (layers - j), startY - dist + dist * (layers - j));
				numNodes++;
				layer.add(node);
				if (i != 0 && withArrows) {
					quiver.addArrow(oldNode, node);
				}
				if ((layers - j) != 0 && withArrows) {
					quiver.addArrow(oldLayer.get(i), node);
					quiver.addArrow(node, oldLayer.get(i + 1));
				}
				oldNode = node;
			}
			oldLayer = layer;
		}
		return quiver;
	}

	private static Quiver createComplete(int n) {
		if (n < 2) {
			return createAn(n, false);
		}
		Quiver quiver = createCycle(n, false);
		quiver.setName("K" + n);
		Set<Node> nodes = quiver.getNodes();
		for (Node node1 : nodes) {
			for (Node node2 : nodes) {
				if (node2 == node1) {
					continue;
				}
				quiver.addArrow(node1, node2);
			}
		}
		return quiver;
	}
}
